﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using YK.Framework.Repository;

namespace YK.Framework.Security
{  
    public class UserService
    {
        IRepository repository;

        public UserService(IRepository repository)
        {
            this.repository = repository;
        }
        
        public void CreateUser(User user)
        {
            repository.Save<User>(user);
        }
        
        public void UpdateUser(User user)
        {
            repository.Update<User>(user);
        }
        
        public void DeleteUser(User user)
        {
            repository.Delete<User>(user);
        }

        public User GetUser(int userID)
        {
            return repository.Get<User>(userID);
        }

        public User GetUserByName(string applicationName, string username)
        {
            Application application = 
                new ApplicationService(repository).GetApplication(applicationName);

            User user =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username == username)
                    .SingleOrDefault();           

            return user; 
        }


        public User GetUserByName(string applicationName, string username, bool updateLastActivity, DateTime currentTimeUtc)
        {
            Application application =
                new ApplicationService(repository).GetApplication(applicationName);

            User user =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username == username)
                    .SingleOrDefault(); 

            if (user != null)
            {              
                if (updateLastActivity)
                {
                    user.LastActivityDate = currentTimeUtc;

                    UpdateUser(user);
                }

                return user;
            }
            
                return null;
            
        }

        public IList<User> GetUsersByEmail(string applicationName, string email)
        {
           
            Application application =
                new ApplicationService(repository).GetApplication(applicationName);

            IList<User> users =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Email == email)
                    .ToList();

            return users;
          
        }

        public int UpdatePassword(string applicationName, string username, string newPassword, string passwordSalt,
            System.Web.Security.MembershipPasswordFormat passwordFormat, DateTime currentTimeUtc)
        {
            Application application =
               new ApplicationService(repository).GetApplication(applicationName);

            User user =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username == username)
                    .SingleOrDefault(); 

            if (user != null)
            {
                user.Password = newPassword;
                user.PasswordSalt = passwordSalt;
                user.PasswordFormat = passwordFormat;
                user.LastPasswordChangedDate = currentTimeUtc;

                UpdateUser(user);

                return 0;
            }
            else
            {
                return 1;
            }

        }

        public int ResetPassword(string applicationName, string username, string newPassword, 
            int maxInvalidPasswordAttempts, int passwordAttemptWindow, string passwordSalt, 
            System.Web.Security.MembershipPasswordFormat passwordFormat, string passwordAnswer, 
            DateTime currentTimeUtc)
        {
            Application application =
              new ApplicationService(repository).GetApplication(applicationName);

            User user =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username == username)
                    .SingleOrDefault(); 

            if (user != null)
            {              
                if (!user.IsLockedOut)
                {
                    if (passwordAnswer == null || passwordAnswer.ToLower().Equals(user.PasswordAnswer))
                    {
                        user.Password = newPassword;
                        user.PasswordFormat = passwordFormat;
                        user.PasswordSalt = passwordSalt;
                        user.LastPasswordChangedDate = currentTimeUtc;
                        user.FailedPasswordAnswerAttemptCount = 0;
                        user.FailedPasswordAnswerAttemptWindowStart = new DateTime(1754, 1, 1);

                        UpdateUser(user);

                        return 0;
                    }
                    else
                    {
                        if (currentTimeUtc > user.FailedPasswordAnswerAttemptWindowStart.AddMinutes(passwordAttemptWindow))
                        {
                            user.FailedPasswordAnswerAttemptCount = 1;
                            user.FailedPasswordAnswerAttemptWindowStart = currentTimeUtc;
                        }
                        else
                        {
                            user.FailedPasswordAnswerAttemptCount++;
                            user.FailedPasswordAnswerAttemptWindowStart = currentTimeUtc;
                        }

                        if (user.FailedPasswordAnswerAttemptCount > maxInvalidPasswordAttempts)
                        {
                            user.IsLockedOut = true;
                            user.LastLockoutDate = currentTimeUtc;
                        }

                        UpdateUser(user);

                        return 3;
                    }
                }
                else
                {
                    return 99;
                }
            }
            else
            {
                return 1;
            }
        }

        public int ChangePasswordQuestionAndAnswer(string applicationName, string username, string newPasswordQuestion,
            string newPasswordAnswer)
        {
            Application application =
               new ApplicationService(repository).GetApplication(applicationName);

            User user =
               repository.Query<User>()
                   .Where(x => x.Application == application)
                   .Where(x => x.Username == username)
                   .SingleOrDefault();

            if (user != null)
            {             
                user.PasswordQuestion = newPasswordQuestion;
                user.PasswordAnswer = newPasswordAnswer;

                UpdateUser(user);

                return 0;
            }
            else
            {
                return 1;
            }

        }

        public int DeleteUser(string applicationName, string username, bool deleteAllRelatedData)
        {
            Application application =
              new ApplicationService(repository).GetApplication(applicationName);

            User user =
               repository.Query<User>()
                   .Where(x => x.Application == application)
                   .Where(x => x.Username == username)
                   .SingleOrDefault();

            if (user != null)
            {              
                DeleteUser(user);

                return 0;
            }
            else
            {
                return 1;
            }
        }

        
        public IList<User> FindUsersByEmail(string applicationName, string emailToMatch, 
            int pageIndex, int pageSize, out int totalRecords)
        {
            Application application =
            new ApplicationService(repository).GetApplication(applicationName);

            IList<User> users =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Email.Contains(emailToMatch))                    
                    .Skip(pageIndex * pageSize)
                    .Take(pageSize)
                    .ToList();           

            totalRecords =
                 repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Email.Contains(emailToMatch))
                    .Count();
           

            return users;
        }

        public IList<User> FindUsersByName(string applicationName, string usernameToMatch,
            int pageIndex, int pageSize, out int totalRecords)
        {
            Application application =
                new ApplicationService(repository).GetApplication(applicationName);

            IList<User> users =
               repository.Query<User>()
                   .Where(x => x.Application == application)
                   .Where(x => x.Username.Contains(usernameToMatch))
                   .Skip(pageIndex * pageSize)
                   .Take(pageSize)
                   .ToList();     

            totalRecords =
                 repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username.Contains(usernameToMatch))
                    .Count();

            return users;
        }
         

        public IList<User> GetAllUsers(string applicationName, 
            int pageIndex, int pageSize, out int totalRecords)
        {
            Application application =
                new ApplicationService(repository).GetApplication(applicationName);

            IList<User> users =
                repository.Query<User>()
                    .Where(x => x.Application == application)       
                    .Skip(pageIndex * pageSize)
                    .Take(pageSize)
                    .ToList();

            totalRecords =
                 repository.Query<User>()
                    .Where(x => x.Application == application)                  
                    .Count();

            return users;
        }
        
        
        public int GetNumberOfUsersOnline(string applicationName, int minutesSinceLastInActive, DateTime currentTimeUtc)
        {
            Application application =
                new ApplicationService(repository).GetApplication(applicationName);          
          
            DateTime activeDate = currentTimeUtc.AddMinutes(-minutesSinceLastInActive);

            int count =
               repository.Query<User>()
                   .Where(x => x.Application == application)
                  .Where(x => x.LastActivityDate > activeDate)
                   .Count();          

            return count;
        }

        public User GetUserByUserID(int userID, bool updateLastActivity, DateTime currentTimeUtc)
        {
            User user = repository.Get<User>(userID);

            if (user != null)
            {
                if (updateLastActivity)
                {
                    user.LastActivityDate = currentTimeUtc;

                    UpdateUser(user);
                }

                return user;
            }
            else
            {
                return null;
            }
        }



        public int UnlockUser(string applicationName, string username)
        {
            Application application =
                new ApplicationService(repository).GetApplication(applicationName);

            User user =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username == username)
                    .SingleOrDefault();

            if (user != null)
            {              
                user.LastLockoutDate = new DateTime(1754, 1, 1);
                user.FailedPasswordAttemptCount = 0;
                user.FailedPasswordAttemptWindowStart = new DateTime(1754, 1, 1);
                user.FailedPasswordAnswerAttemptCount = 0;
                user.FailedPasswordAnswerAttemptWindowStart = new DateTime(1754, 1, 1);

                UpdateUser(user);

                return 0;
            }
            else
            {
                return 1;
            }
        }

        public int UpdateUser(string applicationName, string username, string email, string comment, bool isApproved,
            DateTime lastLoginDate, DateTime lastActivityDate, bool uniqueEmail)
        {
            Application application =
            new ApplicationService(repository).GetApplication(applicationName);

            User user =
                repository.Query<User>()
                    .Where(x => x.Application == application)
                    .Where(x => x.Username == username)
                    .SingleOrDefault();

            if (user != null)
            {             
                if (uniqueEmail)
                {
                    IList<User> users2 =
                        repository.Query<User>()
                            .Where(x => x.Application == application)
                            .Where(x => x.Email == email)
                            .ToList();
                       

                    if (users2.Count > 1)
                    {
                        return 7;
                    }

                    if (users2.Count == 1 && ((User)users2[0]).Id != user.Id)
                    {
                        return 7;
                    }
                }

                user.Email = email;
                user.IsApproved = isApproved;
                user.LastActivityDate = lastActivityDate;
                user.LastLoginDate = lastLoginDate;
                user.Comment = comment;

                UpdateUser(user);
                return 0;
            }
            else
            {
                return 1;
            }
        }

        public int UpdateUserInfo(string applicationName, string username, bool isPasswordCorrect, int passwordAttemptWindow,
            int maxInvalidPasswordAttempts, bool updateLastLoginActivityDate, DateTime currentTimeUtc)
        {
            Application application =
            new ApplicationService(repository).GetApplication(applicationName);

            User user =
               repository.Query<User>()
                   .Where(x => x.Application == application)
                   .Where(x => x.Username == username)
                   .SingleOrDefault();

            if (user != null)
            {                
                if (!user.IsLockedOut)
                {
                    if (!isPasswordCorrect)
                    {
                        if (currentTimeUtc > user.FailedPasswordAttemptWindowStart.AddMinutes(passwordAttemptWindow))
                        {
                            user.FailedPasswordAttemptCount = 1;
                            user.FailedPasswordAttemptWindowStart = currentTimeUtc;
                        }
                        else
                        {
                            user.FailedPasswordAttemptCount++;
                            user.FailedPasswordAttemptWindowStart = currentTimeUtc;
                        }

                        if (user.FailedPasswordAttemptCount > maxInvalidPasswordAttempts)
                        {
                            user.IsLockedOut = true;
                            user.LastLockoutDate = currentTimeUtc;
                        }

                        UpdateUser(user);

                        return 2;
                    }
                    else
                    {
                        user.LastLockoutDate = new DateTime(1754, 1, 1);
                        user.FailedPasswordAttemptCount = 0;
                        user.FailedPasswordAttemptWindowStart = new DateTime(1754, 1, 1);
                        user.FailedPasswordAnswerAttemptCount = 0;
                        user.FailedPasswordAnswerAttemptWindowStart = new DateTime(1754, 1, 1);

                        if (updateLastLoginActivityDate)
                        {
                            user.LastActivityDate = DateTime.UtcNow;
                            user.LastLoginDate = DateTime.UtcNow;
                        }

                        UpdateUser(user);

                        return 0;
                    }
                }
                else
                {
                    return 99;
                }
            }
            else
            {
                return 1;
            }
        }


        public string GetPassword(string applicationName, string username, string passwordAnswer, 
            bool requiresQuestionAndAnswer, int maxInvalidPasswordAttempts, int passwordAttemptWindow, 
            out System.Web.Security.MembershipPasswordFormat passwordFormat, out int status,
            DateTime currentTimeUtc)
        {

            Application application =
            new ApplicationService(repository).GetApplication(applicationName);

            User user =
               repository.Query<User>()
                   .Where(x => x.Application == application)
                   .Where(x => x.Username == username)
                   .SingleOrDefault();

            if (user != null)
            {              
                if (!user.IsLockedOut)
                {
                    if (requiresQuestionAndAnswer)
                    {
                        if (passwordAnswer == null || (!passwordAnswer.ToLower().Equals(user.PasswordAnswer)))
                        {
                            if (currentTimeUtc > user.FailedPasswordAnswerAttemptWindowStart.AddMinutes(passwordAttemptWindow))
                            {
                                user.FailedPasswordAnswerAttemptCount = 1;
                                user.FailedPasswordAnswerAttemptWindowStart = currentTimeUtc;
                            }
                            else
                            {
                                user.FailedPasswordAnswerAttemptCount++;
                                user.FailedPasswordAnswerAttemptWindowStart = currentTimeUtc;
                            }

                            if (user.FailedPasswordAnswerAttemptCount > maxInvalidPasswordAttempts)
                            {
                                user.IsLockedOut = true;
                                user.LastLockoutDate = currentTimeUtc;
                            }

                            status = 3;

                            UpdateUser(user);
                        }
                        else
                        {
                            user.FailedPasswordAnswerAttemptCount = 0;
                            user.FailedPasswordAnswerAttemptWindowStart = new DateTime(1754, 1, 1);

                            UpdateUser(user);

                            status = 0;
                            passwordFormat = user.PasswordFormat;
                            return user.Password;
                        }
                    }
                    else
                    {
                        status = 0;
                        passwordFormat = user.PasswordFormat;
                        return user.Password;
                    }
                }
                else
                {
                    status = 99;
                }
            }
            else
            {
                status = 1;
            }

            passwordFormat = System.Web.Security.MembershipPasswordFormat.Clear;

            return null;
        }
    }
}